package com.tuzhi.base.jpa.repository.impl;

import com.tuzhi.base.jpa.repository.CommonSqlRepo;
import com.tuzhi.util.ListTzUtil;
import com.tuzhi.util.MapTzUtil;
import org.hibernate.query.internal.NativeQueryImpl;
import org.hibernate.transform.Transformers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

@Component
public class CommonRepoImpl implements CommonSqlRepo {

    private static final Logger log = LoggerFactory.getLogger(CommonRepoImpl.class);

    @PersistenceContext
    private EntityManager entityManager;

    JpaEntityInformation entityInformation;

    private <T> JpaEntityInformation getEntityInformation(Class<T> clazz) {
        if (entityInformation == null) {
            entityInformation = JpaEntityInformationSupport.getEntityInformation(clazz, entityManager);
        }
        return entityInformation;
    }

    @Transactional(readOnly = true)
    @Override
    public <T> List<T> findAllByJpqlMap(String jpql, Class<T> c, Map<String, Object> params) {
        Query query = entityManager.createQuery(jpql, c);
        setParams(query, params);
        return query.getResultList();
    }

    @Override
    @Transactional(readOnly = true)
    public <T> List<T> findAllByJpql(String jpql, Class<T> c, Object... params) {
        Query query = entityManager.createQuery(jpql, c);
        setParams(query, params);
        return query.getResultList();
    }

    @Transactional
    @Modifying
    @Override
    public int executeJpqlMap(String jpql, Map<String, Object> params) {
        Query query = entityManager.createQuery(jpql);
        setParams(query, params);
        return query.executeUpdate();
    }

    @Transactional
    @Modifying
    @Override
    public int executeJpql(String jpql, Object... params) {
        Query query = entityManager.createQuery(jpql);
        setParams(query, params);
        return query.executeUpdate();
    }

    @Override
    @Transactional(readOnly = true)
    public <T> List<T> findAllBySqlMap(String sql, Class<T> c, Map<String, Object> params) {
        Query query = entityManager.createNativeQuery(sql, c);
        setParams(query, params);
        query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.aliasToBean(c));
        return query.getResultList();
    }

    @Override
    @Transactional(readOnly = true)
    public <T> List<T> findAllBySql(String sql, Class<T> c, Object... params) {
        Query query = entityManager.createNativeQuery(sql);
        setParams(query, params);
        query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.aliasToBean(c));
        List<T> rs = query.getResultList();
        return rs;
    }

    @Override
    @Transactional(readOnly = true)
    public List findOneColBySql(String sql, Object... params) {
        List<Map> list = findAllMapBySql(sql, params);
        return transOneColList(list);
    }

    @Override
    @Transactional(readOnly = true)
    public List findOneColBySqlMap(String sql, Map<String, Object> params) {
        List<Map> list = findAllMapBySqlMap(sql, params);
        return transOneColList(list);
    }

    @Override
    @Transactional(readOnly = true)
    public Object findSingleResultBySql(String sql, Object... params) {
        List list = findOneColBySql(sql, params);
        if (ListTzUtil.isNotEmpty(list)) {
            return list.get(0);
        }
        return null;
    }

    @Override
    @Transactional(readOnly = true)
    public Object findSingleResultBySqlMap(String sql, Map<String, Object> params) {
        List list = findOneColBySql(sql, params);
        if (ListTzUtil.isNotEmpty(list)) {
            return list.get(0);
        }
        return null;
    }


    @Transactional
    @Modifying
    @Override
    public int executeSqlMap(String sql, Map<String, Object> params) {
        Query query = entityManager.createNativeQuery(sql);
        setParams(query, params);
        return query.executeUpdate();
    }

    @Transactional
    @Modifying
    @Override
    public int executeSql(String sql, Object... params) {
        Query query = entityManager.createNativeQuery(sql);
        setParams(query, params);
        return query.executeUpdate();
    }

    @Transactional(readOnly = true)
    @Override
    public <T> List<T> findAllMapBySqlMap(String sql, Map<String, Object> params) {
        Query query = entityManager.createNativeQuery(sql);
        setParams(query, params);
        query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        return query.getResultList();
    }

    @Transactional(readOnly = true)
    @Override
    public <T> List<T> findAllMapBySql(String sql, Object... params) {
        Query query = entityManager.createNativeQuery(sql);
        setParams(query, params);
        query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        return query.getResultList();
    }

    @Modifying
    @Transactional
    public <T> List<T> batchSaveAll(List<T> list, Integer batchSize) {
        batchSize = batchSize == null ? 1000 : batchSize;
        int count = 0;
        if (ListTzUtil.isEmpty(list)) {
            return list;
        }
        Iterator<T> iterator = list.iterator();
        while (iterator.hasNext()) {
            T t = iterator.next();
            if (entityInformation == null) {
                getEntityInformation(t.getClass());
            }
            count++;
            if (this.entityInformation.isNew(t)) {
                this.entityManager.persist(t);
            } else {
                this.entityManager.merge(t);
            }
            if (count % batchSize == 0) {
                entityManager.flush();
                entityManager.clear();
            }
        }
        entityManager.flush();
        entityManager.clear();
        return list;
    }


    @Modifying
    @Transactional
    public <T> List<T> saveAll(List<T> list) {
        return this.saveAll(list, 1000);
    }

    @Modifying
    @Transactional
    public <T> List<T> saveAll(List<T> list, Integer batchSize) {
        batchSize = batchSize == null ? 1000 : batchSize;
        int count = 0;
        if (ListTzUtil.isEmpty(list)) {
            return list;
        }
        Iterator<T> iterator = list.iterator();
        while (iterator.hasNext()) {
            T t = iterator.next();
            count++;
            entityManager.persist(t);//insert插入操作
            if (count % batchSize == 0) {
                entityManager.flush();
                entityManager.clear();
            }
        }
        entityManager.flush();
        entityManager.clear();
        return list;
    }

    @Modifying
    @Transactional
    public <T> List<T> updateAll(List<T> list) {
        return updateAll(list, 1000);
    }

    @Modifying
    @Transactional
    public <T> List<T> updateAll(List<T> list, Integer batchSize) {
        batchSize = batchSize == null ? 1000 : batchSize;
        int count = 0;
        if (ListTzUtil.isEmpty(list)) {
            return list;
        }
        Iterator<T> iterator = list.iterator();
        while (iterator.hasNext()) {
            T t = iterator.next();
            count++;
            entityManager.merge(t);
            if (count % batchSize == 0) {
                entityManager.flush();
                entityManager.clear();
            }
        }
        entityManager.flush();
        entityManager.clear();
        return list;
    }

    private void setParams(Query query, Map<String, Object> params) {
        if (MapTzUtil.isNotEmpty(params)) {
            for (String key : params.keySet()) {
                query.setParameter(key, params.get(key));
            }
        }
    }

    private void setParams(Query query, Object... params) {
        if (params != null) {
            for (int i = 0; i < params.length; i++) {
                query.setParameter(i + 1, params[i]);
            }
        }
    }

    private List transOneColList(List<Map> list) {
        if (ListTzUtil.isNotEmpty(list)) {
            List rs = new ArrayList();
            list.stream().forEach((Map map) -> {
                Iterator iterator = map.values().iterator();
                rs.add(iterator.next());
            });
            return rs;
        }
        return null;
    }

}
